﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using D2DEngine.Util;
#endregion

namespace D2DEngine.ParticleEffect
{
    public class D2DParticleEffect : D2DSceneObject
    {        
        protected const int DEFAULT_BUFFER_SIZE = 2;
        protected int howManyEffects;

        protected D2DParticle[] particles;
        protected Queue<D2DParticle> freeParticles;

        protected D2DParticleSettings settings;

        /// <summary>
        /// The settings of the particle effect
        /// </summary>
        public D2DParticleSettings Settings
        {
            get { return this.settings; }
            set { this.settings = value; }
        }

        /// <summary>
        /// The number of free particles
        /// </summary>
        public int FreeParticleCount
        {
            get { return freeParticles.Count; }
        }

        /// <summary>
        /// Creates a particle effect
        /// </summary>
        public D2DParticleEffect()
        {
            this.howManyEffects = DEFAULT_BUFFER_SIZE;
            this.settings = new D2DParticleSettings();
        }

        /// <summary>
        /// Creates a particle effect 
        /// </summary>
        /// <param name="settings">The settings of the effect</param>
        public D2DParticleEffect(D2DParticleSettings settings)
        {
            this.howManyEffects = DEFAULT_BUFFER_SIZE;
            this.settings = settings;
        }

        /// <summary>
        /// Creates a particle effect with a minimum buffer size
        /// </summary>
        /// <param name="howManyEffects"></param>
        public D2DParticleEffect(int howManyEffects)
        {
            this.howManyEffects = howManyEffects;
            this.settings = new D2DParticleSettings();
        }

        /// <summary>
        /// Creates a particle effect with a minimum buffer size
        /// </summary>
        /// <param name="howManyEffects"></param>
        /// /// <param name="settings">The settings of the effect</param>
        public D2DParticleEffect(int howManyEffects, D2DParticleSettings settings)
        {
            this.howManyEffects = howManyEffects;
            this.settings = settings;
        }


        public override void  OnRegister()
        {
            particles = new D2DParticle[howManyEffects * settings.MaxParticles];
            freeParticles = new Queue<D2DParticle>(howManyEffects * settings.MaxParticles);

            for (int i = 0; i < particles.Length; i++)
            {
                particles[i] = new D2DParticle();
                freeParticles.Enqueue(particles[i]);
            }

            base.OnRegister();
        }       

        /// <summary>
        /// Adds particle to the effect at a given position
        /// </summary>
        /// <param name="position">The position where the effect should take place</param>
        public void Spawn()
        {
            int numParticles = D2DMath.RandomInt(settings.MinParticles, settings.MinParticles);
            D2DParticle p;

            for (int i = 0; i < numParticles && freeParticles.Count > 0; i++)
            {
                p = freeParticles.Dequeue();
                InitializeParticle(p);
            }
        }

        /// <summary>
        /// Initializes a particle according to the
        /// effect's settings
        /// </summary>
        /// <param name="p">The particle</param>
        /// <param name="position">The starting position of the particle</param>
        protected virtual void InitializeParticle(D2DParticle p)
        {
            Vector2 direction = PickRandomDirection();

            float velocity =
                D2DMath.RandomFloat(settings.MinVelocity, settings.MaxVelocity);
            float acceleration =
                D2DMath.RandomFloat(settings.MinAcceleration, settings.MaxAcceleration);
            float lifetime =
                D2DMath.RandomFloat(settings.MinLifetime, settings.MaxLifetime);
            float scale =
                D2DMath.RandomFloat(settings.MinScale, settings.MaxScale);
            float rotationSpeed =
                D2DMath.RandomFloat(settings.MinAngularVelocity, settings.MaxAngularVelocity);

            p.Initialize(
                this.position, velocity * direction, acceleration * direction,
                lifetime, scale, rotationSpeed);
        }

        
        /// <summary>
        /// Returns a random direction between 0 and 2*pi
        /// </summary>
        /// <returns>The direction vector</returns>
        protected virtual Vector2 PickRandomDirection()
        {
            float angle = D2DMath.RandomFloat(0, MathHelper.TwoPi);

            return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
        }


        /// <summary>
        /// Updates the particle effect
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            foreach (D2DParticle p in particles)
            {
                if (p.IsAlive)
                {
                    p.Update(dt);

                    if (!p.IsAlive)
                    {
                        freeParticles.Enqueue(p);
                    }
                }
            }

            base.Update(gameTime);
        }


        /// <summary>
        /// Draws the particle effect
        /// </summary>
        /// <param name="spriteBatch"></param>
        public override void Draw(SpriteBatch spriteBatch)
        {
            Color spriteColor = Sprite.Color;

            foreach (D2DParticle p in particles)
            {
                if (!p.IsAlive)
                    continue;
                
                float normalizedLifetime = p.TimeSinceStart / p.Lifetime;

                // Fade out the particles as they age
                float alpha = 4 * normalizedLifetime * (1 - normalizedLifetime);
                Color color = new Color(spriteColor.R, spriteColor.G, spriteColor.B, alpha);

                // Enlarge particles as they age
                float scale = p.Scale * (.75f + .25f * normalizedLifetime);

                spriteBatch.Draw(
                    this.sprite.Texture, 
                    p.Position, 
                    null, 
                    color,
                    p.Rotation, 
                    this.sprite.Origin, 
                    scale, 
                    SpriteEffects.None, 
                    this.layer / 10);
            }
        }
        
    }
}
